/*
 * Experimental multi-party voice chat
 */

import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import javax.sound.sampled.*;

public class MultiAudioChat {
	static ByteArrayOutputStream outgoing;
	static ByteArrayOutputStream incoming;
	static ByteArrayOutputStream incoming2;
	static AudioFormat format;
	static float sampleRate;
	static int sampleSizeInBits;
	static int channels;
	static boolean signed;
	static boolean bigEndian;

	static String ip;
	static String ip2;
	static int count;

	static pThread p;
	static pThread p2;
	static rThread r;
	static sThread s;

	public static void main(String[] args) throws IOException {

		count = 1;

		// specify audio format
		sampleRate = 8000;
		sampleSizeInBits = 8;
		channels = 1;
		signed = true;
		bigEndian = true;
		format = new AudioFormat(sampleRate, sampleSizeInBits, channels,
				signed, bigEndian);

		ip = "10.0.0.152";
		ip2 = "10.0.0.200";

		r = new rThread();
		r.start();

		listen();
	}

	public static void startSession(String s) throws IOException {
		ip = s;
		count = 1;

		// specify audio format
		sampleRate = 8000;
		sampleSizeInBits = 8;
		channels = 1;
		signed = true;
		bigEndian = true;
		format = new AudioFormat(sampleRate, sampleSizeInBits, channels,
				signed, bigEndian);

		// starts recording
		r = new rThread();
		r.start();

		// starts listening and playing back
		listen();
	}

	static class rThread extends Thread {
		public void run() {
			record();
		}
	}

	static class sThread extends Thread {
		public void run() {
			while (true) {
				try {
					send();
				} catch (InterruptedException e) {
					System.err.println("Interrupted exception");
					System.exit(-1);
				}
			}
		}
	}

	static class pThread extends Thread {

		private ByteArrayOutputStream source;

		public pThread(ByteArrayOutputStream baos) {
			this.source = baos;
		}

		public void run() {
			try {
				while (true) {
					playBack(source);
				}
			} catch (IOException e) {
				System.err.println("I/O exception: unable to playback");
				System.exit(-1);
			}
		}
	}

	private static void record() {
		DataLine.Info info = new DataLine.Info(TargetDataLine.class, format);
		TargetDataLine line = null;
		try {
			line = (TargetDataLine) AudioSystem.getLine(info);
			line.open(format);
		} catch (LineUnavailableException e) {
			System.err.println("Line unavailable");
			System.exit(-1);
		}

		// starts capturing
		line.start();

		int bufferSize = (int) format.getSampleRate() * format.getFrameSize();

		byte buffer[] = new byte[bufferSize];
		outgoing = new ByteArrayOutputStream();
		int c = 1;

		int count = line.read(buffer, 0, buffer.length);
		if (count > 0) {
			outgoing.write(buffer, 0, count);
			System.out.println("Now recording for " + c + " seconds");
		}
		c++;

		sThread s = new sThread();
		s.start();

		while (true) {
			count = line.read(buffer, 0, buffer.length);
			if (count > 0) {
				outgoing.write(buffer, 0, count);
				System.out.println("Now recording for " + c + " seconds");
			}
			c++;
		}
	}

	private static void send() throws InterruptedException {
		byte audio[] = outgoing.toByteArray();
		outgoing.reset();

		int byteToSend = audio.length;
		int remain = audio.length;

		InetAddress address = null;
		InetAddress address2 = null;
		try {
			address = InetAddress.getByName(ip);
			address2 = InetAddress.getByName(ip2);
		} catch (UnknownHostException e) {
			System.err.println("Unknown host");
			System.exit(-1);
		}

		// Open a datagram socket
		DatagramSocket socket = null;
		DatagramSocket socket2 = null;
		try {
			socket = new DatagramSocket();
			socket2 = new DatagramSocket();
		} catch (SocketException e) {
			System.err
					.println("Line 180 Socket exception: unable to open socket");
			System.exit(-1);
		}

		socket.connect(address, 55556);
		socket2.connect(address2, 55556);

		while (remain > 0) {
			if (remain > 1024) {
				DatagramPacket packet;
				try {
					packet = new DatagramPacket(audio, byteToSend - remain,
							1024, address, 55556);
					socket.send(packet);

					packet = new DatagramPacket(audio, byteToSend - remain,
						 1024, address2, 55556);
					socket2.send(packet);
					System.out.println("Packet #" + count + " sent");
					//Thread.currentThread().sleep(50);
					remain -=1024;
					System.out.println("remaining bytes: " + remain);
					count++;
				} catch (IOException e) {
					System.err.println("I/O exception: unable to send packet");
					System.exit(-1);
				}
			} else {
				DatagramPacket packet;
				try {
					packet = new DatagramPacket(audio, byteToSend - remain,
							remain, address, 55556);
					socket.send(packet);

					packet = new DatagramPacket(audio, byteToSend - remain,
							remain, address2, 55556);
					socket2.send(packet);
					System.out.println("Packet sent");
					System.out.println("Packet #" + count + " sent");
					//Thread.currentThread().sleep(50);
					remain = 0;
				} catch (IOException e) {
					System.err.println("I/O exception: unable to send packet");
					System.exit(-1);
				}
			}
		}
		socket.close();
		socket2.close();
	}

	/**
	 * Accepts audio from a remote host and plays it
	 * 
	 * @throws IOException
	 */
	private static void listen() throws IOException {

		boolean inputIsEmpty = true;
		boolean threadStarted = false;

		incoming = new ByteArrayOutputStream(1024);
		incoming2 = new ByteArrayOutputStream(1024);

		int counter = 0;
		byte[] temp = new byte[1024];

		DatagramSocket lsocket = null;
		DatagramPacket packet = null;

		try {
			lsocket = new DatagramSocket(55556);
		} catch (SocketException e) {
			System.err
					.println("Line 251 Socket exception: unable to open socket");
			System.exit(-1);
		}

		while (true) {

			if (!threadStarted && counter > 5) {
				inputIsEmpty = false;
			}

			packet = new DatagramPacket(temp, temp.length);

			try {
				System.out.println("Now listening...");
				lsocket.receive(packet);
				// lsocket.close();
				String org = packet.getAddress().toString();
				if (org.equals("/10.0.0.152")) {
					incoming.write(packet.getData());
				} else if (org.equals("/10.0.0.200")) {
					incoming2.write(packet.getData());
				}
				System.out.println("Packet received from " + org + ", count = "
						+ counter++);
			} catch (IOException e) {
				System.err.println("Unable to receive packet");
				System.exit(-1);
			}

			if (!threadStarted && !inputIsEmpty) {
				threadStarted = true;
				p = new pThread(incoming);
				p.start();
				p2 = new pThread(incoming2);
				p2.start();
			}

		}
	}

	/**
	 * Plays audio from a ByteArrayOutputStream
	 * 
	 * @param baos
	 * @throws IOException
	 */
	private static void playBack(ByteArrayOutputStream baos) throws IOException {

		byte buffer[] = baos.toByteArray();
		baos.reset();

		AudioInputStream ais = new AudioInputStream(new ByteArrayInputStream(
				buffer), format, buffer.length / format.getFrameSize());

		DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
		SourceDataLine pline = null;
		try {
			pline = (SourceDataLine) AudioSystem.getLine(info);
			pline.open(format);
		} catch (LineUnavailableException e) {
			System.err.println("Line unavailable");
			System.exit(-1);
		}

		pline.start();

		int count;
		while ((count = ais.read(buffer, 0, buffer.length)) != -1) {
			if (count > 0) {
				pline.write(buffer, 0, count);
			}
		}
		pline.drain();
		pline.close();
	}

	/**
	 * For playback on local machine
	 * 
	 * @throws IOException
	 */
	private static void playBack() throws IOException {
		byte audio[] = outgoing.toByteArray();
		outgoing.reset();
		AudioInputStream ais = new AudioInputStream(new ByteArrayInputStream(
				audio), format, audio.length / format.getFrameSize());

		DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
		SourceDataLine pline = null;
		try {
			pline = (SourceDataLine) AudioSystem.getLine(info);
			pline.open(format);
		} catch (LineUnavailableException e) {
			System.err.println("Line unavailable");
			System.exit(-1);
		}

		pline.start();

		int bufferSize = (int) format.getSampleRate() * format.getFrameSize();
		byte buffer[] = new byte[bufferSize];

		int count;
		while ((count = ais.read(buffer, 0, buffer.length)) != -1) {
			if (count > 0) {
				pline.write(buffer, 0, count);
			}
		}
		pline.drain();
		pline.close();
	}
}
